package com.yqw.onlineDocs.manager.websocket;

import cn.hutool.core.collection.CollUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.yqw.onlineDocs.manager.websocket.model.DocumentEditMessageTypeEnum;
import com.yqw.onlineDocs.manager.websocket.model.DocumentEditOperationEnum;
import com.yqw.onlineDocs.manager.websocket.model.DocumentEditRequestMessage;
import com.yqw.onlineDocs.manager.websocket.model.DocumentEditResponseMessage;
import com.yqw.onlineDocs.model.entity.Document;
import com.yqw.onlineDocs.model.entity.DocumentVersion;
import com.yqw.onlineDocs.model.entity.User;
import com.yqw.onlineDocs.service.DocumentService;
import com.yqw.onlineDocs.service.DocumentVersionService;
import com.yqw.onlineDocs.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.annotation.Resource;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

/**
 * WebSocket处理器
 */
@Component
@Slf4j
public class DocumentEditHandler extends TextWebSocketHandler {

  @Resource
  private UserService userService;

  @Resource
  private DocumentService documentService;

  @Resource
  private DocumentVersionService documentVersionService;

  // 每个文档的编辑状态，key: documentId, value: 当前正在编辑的用户集合
  private final Map<Long, Set<Long>> documentEditingUsers = new ConcurrentHashMap<>();

  // 保存所有连接的会话，key: documentId, value: 用户会话集合
  private final Map<Long, Map<Long, WebSocketSession>> documentSessions = new ConcurrentHashMap<>();

  // 文档内容缓存，key: documentId, value: 当前内容
  private final Map<Long, String> documentContents = new ConcurrentHashMap<>();

  // 文档锁，用于并发控制
  private final Map<Long, ReentrantLock> documentLocks = new ConcurrentHashMap<>();

  /**
   * 广播消息给文档的所有会话
   */
  private void broadcastToDocument(Long documentId, DocumentEditResponseMessage responseMessage,
      WebSocketSession excludeSession) throws Exception {
    Map<Long, WebSocketSession> sessionMap = documentSessions.get(documentId);
    if (CollUtil.isNotEmpty(sessionMap)) {
      // 创建 ObjectMapper
      ObjectMapper objectMapper = new ObjectMapper();
      // 配置序列化：将 Long 类型转为 String，解决丢失精度问题
      SimpleModule module = new SimpleModule();
      module.addSerializer(Long.class, ToStringSerializer.instance);
      module.addSerializer(Long.TYPE, ToStringSerializer.instance);
      objectMapper.registerModule(module);
      // 序列化为 JSON 字符串
      String message = objectMapper.writeValueAsString(responseMessage);
      TextMessage textMessage = new TextMessage(message);

      for (WebSocketSession session : sessionMap.values()) {
        // 排除掉的 session 不发送
        if (excludeSession != null && excludeSession.equals(session)) {
          continue;
        }
        if (session.isOpen()) {
          session.sendMessage(textMessage);
        }
      }
    }
  }

  /**
   * 广播给所有会话
   */
  private void broadcastToDocument(Long documentId, DocumentEditResponseMessage responseMessage) throws Exception {
    broadcastToDocument(documentId, responseMessage, null);
  }

  @Override
  public void afterConnectionEstablished(WebSocketSession session) throws Exception {
    // 保存会话到集合中
    User user = (User) session.getAttributes().get("user");
    Long userId = (Long) session.getAttributes().get("userId");
    Long documentId = (Long) session.getAttributes().get("documentId");

    // 初始化文档会话集合
    documentSessions.putIfAbsent(documentId, new ConcurrentHashMap<>());
    documentSessions.get(documentId).put(userId, session);

    // 初始化文档编辑用户集合
    documentEditingUsers.putIfAbsent(documentId, ConcurrentHashMap.newKeySet());
    documentEditingUsers.get(documentId).add(userId);

    // 初始化文档锁
    documentLocks.putIfAbsent(documentId, new ReentrantLock());

    // 初始化文档内容（如果尚未初始化）
    if (!documentContents.containsKey(documentId)) {
      Document document = documentService.getById(documentId);
      documentContents.put(documentId, document.getContent());
    }

    // 构造响应
    DocumentEditResponseMessage responseMessage = new DocumentEditResponseMessage();
    responseMessage.setType(DocumentEditMessageTypeEnum.INFO.getValue());
    String message = String.format("%s加入协作编辑", user.getUserName());
    responseMessage.setMessage(message);
    responseMessage.setUser(userService.getUserVO(user));

    // 广播给同一文档的用户
    broadcastToDocument(documentId, responseMessage);

    // 发送当前文档内容给新加入的用户
    DocumentEditResponseMessage contentMessage = new DocumentEditResponseMessage();
    contentMessage.setType(DocumentEditMessageTypeEnum.INFO.getValue());
    contentMessage.setMessage("当前文档内容");
    contentMessage.setContent(documentContents.get(documentId));
    session.sendMessage(new TextMessage(new ObjectMapper().writeValueAsString(contentMessage)));

    // 发送当前在线用户列表给新加入的用户
    Set<Long> currentEditingUsers = documentEditingUsers.get(documentId);
    if (currentEditingUsers != null && !currentEditingUsers.isEmpty()) {
      for (Long existingUserId : currentEditingUsers) {
        if (!existingUserId.equals(userId)) { // 排除自己
          User existingUser = userService.getById(existingUserId);
          if (existingUser != null) {
            DocumentEditResponseMessage userJoinMessage = new DocumentEditResponseMessage();
            userJoinMessage.setType(DocumentEditMessageTypeEnum.INFO.getValue());
            userJoinMessage.setMessage(String.format("%s加入协作编辑", existingUser.getUserName()));
            userJoinMessage.setUser(userService.getUserVO(existingUser));
            session.sendMessage(new TextMessage(new ObjectMapper().writeValueAsString(userJoinMessage)));
          }
        }
      }
    }

    log.info("用户{}加入文档{}的协作编辑", userId, documentId);
  }

  @Override
  protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
    try {
      // 解析消息
      DocumentEditRequestMessage requestMessage = new ObjectMapper().readValue(message.getPayload(),
          DocumentEditRequestMessage.class);
      String type = requestMessage.getType();
      DocumentEditMessageTypeEnum messageTypeEnum = DocumentEditMessageTypeEnum.getEnumByValue(type);

      // 从 Session 属性中获取公共参数
      Map<String, Object> attributes = session.getAttributes();
      User user = (User) attributes.get("user");
      Long userId = (Long) attributes.get("userId");
      Long documentId = (Long) attributes.get("documentId");

      // 根据消息类型处理
      switch (messageTypeEnum) {
        case INIT:
          handleInit(requestMessage, session, user, userId, documentId);
          break;
        case EDIT_ACTION:
          handleEditAction(requestMessage, session, user, userId, documentId);
          break;
        case CURSOR_MOVE:
          handleCursorMove(requestMessage, session, user, userId, documentId);
          break;
        case SELECTION_CHANGE:
          handleSelectionChange(requestMessage, session, user, userId, documentId);
          break;
        case SAVE_VERSION:
          handleSaveVersion(requestMessage, session, user, userId, documentId);
          break;
        default:
          DocumentEditResponseMessage errorResponse = new DocumentEditResponseMessage();
          errorResponse.setType(DocumentEditMessageTypeEnum.ERROR.getValue());
          errorResponse.setMessage("不支持的消息类型");
          errorResponse.setUser(userService.getUserVO(user));
          session.sendMessage(new TextMessage(new ObjectMapper().writeValueAsString(errorResponse)));
      }
    } catch (Exception e) {
      log.error("处理WebSocket消息失败", e);
      DocumentEditResponseMessage errorResponse = new DocumentEditResponseMessage();
      errorResponse.setType(DocumentEditMessageTypeEnum.ERROR.getValue());
      errorResponse.setMessage("消息处理失败: " + e.getMessage());
      session.sendMessage(new TextMessage(new ObjectMapper().writeValueAsString(errorResponse)));
    }
  }

  /**
   * 处理编辑操作
   */
  private void handleEditAction(DocumentEditRequestMessage requestMessage, WebSocketSession session,
      User user, Long userId, Long documentId) throws Exception {
    String operation = requestMessage.getOperation();
    String content = requestMessage.getContent();
    Integer position = requestMessage.getPosition();

    // 获取文档锁
    ReentrantLock lock = documentLocks.get(documentId);
    lock.lock();
    try {

      // 获取当前文档内容
      String currentContent = documentContents.get(documentId);
      String newContent;

      // 根据操作类型更新内容
      DocumentEditOperationEnum operationEnum = DocumentEditOperationEnum.getEnumByValue(operation);
      if (operationEnum == null) {
        return;
      }

      switch (operationEnum) {
        case INSERT:
          // 在指定位置插入内容
          newContent = currentContent.substring(0, position) + content + currentContent.substring(position);
          break;
        case DELETE:
          // 删除指定位置的内容
          int endPosition = position + content.length();
          if (endPosition > currentContent.length()) {
            endPosition = currentContent.length();
          }
          newContent = currentContent.substring(0, position) + currentContent.substring(endPosition);
          break;
        case REPLACE:
          // 替换指定位置的内容
          int replaceEnd = position + content.length();
          if (replaceEnd > currentContent.length()) {
            replaceEnd = currentContent.length();
          }
          newContent = currentContent.substring(0, position) + content + currentContent.substring(replaceEnd);
          break;
        default:
          return;
      }

      // 更新文档内容
      documentContents.put(documentId, newContent);

      // 构造响应消息
      DocumentEditResponseMessage responseMessage = new DocumentEditResponseMessage();
      responseMessage.setType(DocumentEditMessageTypeEnum.EDIT_ACTION.getValue());
      responseMessage.setOperation(operation);
      responseMessage.setContent(content);
      responseMessage.setPosition(position);
      responseMessage.setUser(userService.getUserVO(user));

      // 广播给其他用户
      broadcastToDocument(documentId, responseMessage, session);
    } finally {
      lock.unlock();
    }
  }

  /**
   * 处理光标移动
   */
  private void handleCursorMove(DocumentEditRequestMessage requestMessage, WebSocketSession session,
      User user, Long userId, Long documentId) throws Exception {
    Integer position = requestMessage.getPosition();

    // 构造响应消息
    DocumentEditResponseMessage responseMessage = new DocumentEditResponseMessage();
    responseMessage.setType(DocumentEditMessageTypeEnum.CURSOR_MOVE.getValue());
    responseMessage.setPosition(position);
    responseMessage.setUser(userService.getUserVO(user));

    // 广播给其他用户
    broadcastToDocument(documentId, responseMessage, session);
  }

  /**
   * 处理选择区域变化
   */
  private void handleSelectionChange(DocumentEditRequestMessage requestMessage, WebSocketSession session,
      User user, Long userId, Long documentId) throws Exception {
    Integer position = requestMessage.getPosition();
    String content = requestMessage.getContent(); // 用于存储选择的长度或结束位置

    // 构造响应消息
    DocumentEditResponseMessage responseMessage = new DocumentEditResponseMessage();
    responseMessage.setType(DocumentEditMessageTypeEnum.SELECTION_CHANGE.getValue());
    responseMessage.setPosition(position);
    responseMessage.setContent(content);
    responseMessage.setUser(userService.getUserVO(user));

    // 广播给其他用户
    broadcastToDocument(documentId, responseMessage, session);
  }

  /**
   * 处理保存版本
   */
  private void handleSaveVersion(DocumentEditRequestMessage requestMessage, WebSocketSession session,
      User user, Long userId, Long documentId) throws Exception {
    // 获取文档锁
    ReentrantLock lock = documentLocks.get(documentId);
    lock.lock();
    try {
      // 获取当前文档内容
      String currentContent = documentContents.get(documentId);

      // 更新数据库中的文档内容
      Document document = documentService.getById(documentId);
      document.setContent(currentContent);
      documentService.updateById(document);

      // 构造响应消息
      DocumentEditResponseMessage responseMessage = new DocumentEditResponseMessage();
      responseMessage.setType(DocumentEditMessageTypeEnum.SAVE_VERSION.getValue());
      responseMessage.setMessage(String.format("%s保存了文档", user.getUserName()));
      responseMessage.setUser(userService.getUserVO(user));

      // 广播给所有用户
      broadcastToDocument(documentId, responseMessage);
    } finally {
      lock.unlock();
    }
  }

  @Override
  public void afterConnectionClosed(WebSocketSession session, @NotNull CloseStatus status) throws Exception {
    // 获取会话属性
    Map<String, Object> attributes = session.getAttributes();
    User user = (User) attributes.get("user");
    Long userId = (Long) attributes.get("userId");
    Long documentId = (Long) attributes.get("documentId");

    // 从编辑用户集合中移除
    Set<Long> editingUsers = documentEditingUsers.get(documentId);
    if (editingUsers != null) {
      editingUsers.remove(userId);
      if (editingUsers.isEmpty()) {
        documentEditingUsers.remove(documentId);
      }
    }

    // 从会话集合中移除
    Map<Long, WebSocketSession> sessionMap = documentSessions.get(documentId);
    if (sessionMap != null) {
      sessionMap.remove(userId);
      if (sessionMap.isEmpty()) {
        documentSessions.remove(documentId);
        // 如果没有用户在编辑，可以清理相关资源
        documentContents.remove(documentId);
        documentLocks.remove(documentId);
      }
    }

    // 构造响应消息
    DocumentEditResponseMessage responseMessage = new DocumentEditResponseMessage();
    responseMessage.setType(DocumentEditMessageTypeEnum.INFO.getValue());
    String message = String.format("%s离开协作编辑", user.getUserName());
    responseMessage.setMessage(message);
    responseMessage.setUser(userService.getUserVO(user));

    // 广播给其他用户
    broadcastToDocument(documentId, responseMessage);

  }

  /**
   * 处理初始化连接
   */
  private void handleInit(DocumentEditRequestMessage requestMessage, WebSocketSession session, User user, Long userId,
      Long documentId) throws Exception {
    log.debug("处理初始化连接消息: 用户ID={}, 文档ID={}", userId, documentId);

    // 获取文档内容
    Document document = documentService.getById(documentId);
    if (document == null) {
      DocumentEditResponseMessage errorResponse = new DocumentEditResponseMessage();
      errorResponse.setType(DocumentEditMessageTypeEnum.ERROR.getValue());
      errorResponse.setMessage("文档不存在");
      errorResponse.setUser(userService.getUserVO(user));
      session.sendMessage(new TextMessage(new ObjectMapper().writeValueAsString(errorResponse)));
      return;
    }

    // 发送文档内容和版本信息
    DocumentEditResponseMessage responseMessage = new DocumentEditResponseMessage();
    responseMessage.setType(DocumentEditMessageTypeEnum.INFO.getValue());
    responseMessage.setMessage("连接成功，获取文档内容");
    responseMessage.setContent(document.getContent());
    responseMessage.setUser(userService.getUserVO(user));

    session.sendMessage(new TextMessage(new ObjectMapper().writeValueAsString(responseMessage)));

    // 通知其他用户有新用户加入
    DocumentEditResponseMessage joinMessage = new DocumentEditResponseMessage();
    joinMessage.setType(DocumentEditMessageTypeEnum.INFO.getValue());
    joinMessage.setMessage(String.format("%s加入协作编辑", user.getUserName()));
    joinMessage.setUser(userService.getUserVO(user));

    // 广播给其他用户（不包括当前用户）
    broadcastToDocument(documentId, joinMessage, session);
  }
}
